見出し画像

Swiftでlazyの使い方を復習してみる

こんにちは、iOSエンジニアのTanです。

普段はLazyを意識せずに実装してきましたが、さまざまなライブラリのソースコードを拝見したところ、lazyが使われている場所が多く、改めて復習してみたいと思います。

Swiftのドキュメントはlazyがこんな感じで定義されてます。

A lazy stored property is a property whose initial value is not calculated until the first time it is used

使われるまでに、初期valueが計算されない意味ですね。

lazyをつけたPropertyは下記のような特徴があります。

・初期値がインスタンス生成後の状態(他のプロパティなど)に依存している
・セットアップが複雑で重く、使われるまでは生成する必要がない

さて、実際のコードをみてみましょう。

struct Person {
   let name: String
   let age: Int
}

struct PeopleViewModel {
   let people: [Person]
   
   lazy var oldest: Person? = {
       print("Lazy var oldest initialized")
       return people.max(by: { $0.age < $1.age })
   }()
   
   init(people: [Person]) {
       self.people = people
       print("View model initialized")
   }
}

PeopleViewModelを注目してください。oldestにlazyがつけられて、一回だけの計算で、peopleな中に一番お年寄りの人を取り出したいわけですね。

var viewModel = PeopleViewModel(people: [
   Person(name: "Antoine", age: 30),
   Person(name: "Jaap", age: 3),
   Person(name: "Lady", age: 3),
   Person(name: "Maaike", age: 27)
])
// Prints: "View model initialized"
print(viewModel.oldest)
// Prints: "Lazy var oldest initialized"
// Prints: Person(name: "Antoine", age: 30)

上記のコードをみると、oldestはviewModelのinstanceが生成された時にコールされなく、oldestがコールされたら実行されます。まさにセットアップが複雑で重く、使われるまでは生成する必要がないということですね。

続いて、初期値がインスタンス生成後の状態(他のプロパティなど)に依存しているをみていきたいです。

struct PeopleViewModel {
   var people: [Person]
   
   lazy var oldest: Person? = {
       print("oldest person calculated")
       return people.max(by: { $0.age < $1.age })
   }()
}
var viewModel = PeopleViewModel(people: [
   Person(name: "Antoine", age: 30),
   Person(name: "Jaap", age: 3),
   Person(name: "Lady", age: 3),
   Person(name: "Maaike", age: 27)
])
print(viewModel.oldest)
// Prints: "oldest person calculated"
// Prints: Person(name: "Antoine", age: 30)

viewModel.people.append(Person(name: "Jan", age: 69))
print(viewModel.oldest)
// Prints: Person(name: "Antoine", age: 30)

最後の三行を注目してください。前のデータより年が高い人(Jan)を追加したのに、初期値に依存してしまっているため、printされたデータは前のvalueでした。

computed propertyとlazy stored propertyの違い

computed propertyの場合

struct PeopleViewModel {
   let people: [Person]
   
   var oldest: Person? {
       print("oldest person calculated")
       return people.max(by: { $0.age < $1.age })
   }
}
print(viewModel.oldest)
// Prints: "oldest person calculated"
// Prints: Person(name: "Antoine", age: 30)
print(viewModel.oldest)
// Prints: "oldest person calculated"
// Prints: Person(name: "Antoine", age: 30)

lazy stored propertyの場合

struct PeopleViewModel {
   let people: [Person]
   
   lazy var oldest: Person? = {
       print("oldest person calculated")
       return people.max(by: { $0.age < $1.age })
   }()
}
print(viewModel.oldest)
// Prints: "oldest person calculated"
// Prints: Person(name: "Antoine", age: 30)
print(viewModel.oldest)
// Prints: Person(name: "Antoine", age: 30)

ご覧のとおりcomputed propertyはコールされたたびに、毎回再計算になってしまいます。逆に、lazy stored propertyは一回だけコールされ、パフォーマンスはよくなります。

まとめ

lazy stored propertyの使い方少しだけわかってきたでしょうか?これからも実際のプロジェクトに取り組んでいきたいと思います。

この記事が気に入ったらサポートをしてみませんか?